
import { AdsorbType, AlignType, ClassName, CtrlType, Events } from "../Constants";
import GridSystem from "../GridSystem";
import type MiniMap from "../MiniMap";
import { IPoint, IPixelPos, IRelativePos, ISize, Listeners } from "../Interface";
import { Utils } from "../utils";
import Pnt from "./function-shape/Pnt";
import type Group from "./function-shape/Group";

interface PntPerOfBBox {
    toLeft: IPoint[],
    toRight: IPoint[],
}

class Feature {

    static Gls: GridSystem;  // GridSystem实例
    static Render: GridSystem | MiniMap;  // 当前画布实例是哪一个  GridSystem实例 或 MiniMap实例
    static minDistanceOfTwoPnt: number = 0.1;  // 元素添加时,俩点之间太近就不添加,设置的最小距离参数
    static getRectWrapExtent(pointArr: IPoint[], padding = 0): number[] {  // 获取元素包围盒矩形的上下左右边距
        let minX = Infinity;
        let maxX = -Infinity;
        let minY = Infinity;
        let maxY = -Infinity;
        for (const point of pointArr) {
            minX = Math.min(minX, point.x);
            maxX = Math.max(maxX, point.x);
            minY = Math.min(minY, point.y);
            maxY = Math.max(maxY, point.y);
        }
        return [minX - padding, maxX + padding, minY - padding, maxY + padding];  // [left, right, top, bottom]
    }
    static getRectWrapPoints(pointArr: IPoint[], padding = 0): IPoint[] { // [leftTop, rightTop, rightBottom, leftBottom]  获取元素包围盒矩形的四个点
        const [minX, maxX, minY, maxY] = Feature.getRectWrapExtent(pointArr, padding);
        if (minX != null && minY != null && maxX != null && maxY != null) {
            return [
                { x: minX, y: minY },
                { x: maxX, y: minY },
                { x: maxX, y: maxY },
                { x: minX, y: maxY },
            ]
        }
        return []
    }
    static getCenterPos(pointArr: IPoint[]): IPoint & Partial<ISize> {  // 获取元素包围盒的中心点
        if (pointArr.length === 1) {
            return pointArr[0]
        }
        const [minX, maxX, minY, maxY] = Feature.getRectWrapExtent(pointArr);
        return {
            x: (minX + maxX) / 2,
            y: (minY + maxY) / 2,
            width: Math.abs(minX - maxX),
            height: Math.abs(minY - maxY),
        }
    }
    static getRectEdges(pointArr: IPoint[], padding = 0): IPoint[][] { // 上, 右, 下, 左
        const [minX, maxX, minY, maxY] = Feature.getRectWrapExtent(pointArr, padding);
        if (minX != null && minY != null && maxX != null && maxY != null) {
            return [
                [{ x: minX, y: minY }, { x: maxX, y: minY },],
                [{ x: maxX, y: minY }, { x: maxX, y: maxY },],
                [{ x: maxX, y: maxY }, { x: minX, y: maxY },],
                [{ x: minX, y: maxY }, { x: minX, y: minY },],
            ]
        }
        return []
    }
    static isShowAdsorbLine = false;

    private _listeners: Listeners = {};
    [key: string]: any;

    // 节点标识
    id: string  // id,元素的唯一标识
    name: string = ''  // 元素的name, 给当前元素起个名字
    className = ClassName.FEATURE  //类名

    pointArr: IRelativePos[] = [];   // 点坐标集合(原始坐标点),决定几何形状
    getGeneratePointArrFn: Function | null = null;  // 根据原始坐标(pointArr)集合设置一定规则生成新的点坐标集合
    bezierPnts: IPoint[] = []; // 贝塞尔曲线控制点坐标存储

    // 节点样式 
    strokeStyle: string | Function = '#000';
    _fillStyle: string | Function = 'transparent';
    get fillStyle() { return this._fillStyle }
    set fillStyle(val) {
        if (!this.hoverStyle) { this.hoverStyle = this.fillStyle }
        if (!this.focusStyle) { this.focusStyle = this.fillStyle }
        if (!this.strokeStyle) { this.strokeStyle = this.fillStyle }
        this._fillStyle = val;
    }
    _hoverStyle: string | Function = '';
    get hoverStyle() { return this._hoverStyle }
    set hoverStyle(val) {
        if (!this.focusStyle) { this.focusStyle = this.hoverStyle }
        if (!this.strokeStyle) { this.strokeStyle = this.hoverStyle }
        this._hoverStyle = val;
    }
    focusStyle: string | Function = '';
    zIndex: number = 0;
    lineWidth: number = .2;  // 描边宽度
    fakeLineWidth: number = 10;  // 透明的描边宽度,比如线段太细时不好被选取,再画一个透明的粗的
    lineCap: CanvasLineCap = "round"   // 线段端点处的样式 butt, round, square
    lineJoin: CanvasLineJoin = "round"  // 线段之间连接处的样式 butt, round, square
    opacity: number = 1; // 整体透明度
    lineDashArr: number[] = [];  // 虚线
    lineDashOffset: number = 0; // 虚线位移
    margin = 0;  // 外边距
    padding = 0;  // 外边距
    shadowColor = '';
    shadowBlur = 0;
    shadowOffsetX = 0;
    shadowOffsetY = 0;
    filter = ''
    angle: number = 0; // 元素旋转的角度
    // get angle() { return this._angle }
    // set angle(val) {
    //     // this.angle = 
    //     this.rotate(val)
    // }
    visible: boolean = true;   // 是否隐藏元素,跳过渲染

    // 关系节点
    parent: Feature | null = null;  // 元素的父元素
    group: Group | null = null;  // 所属的组
    children: Feature[] = [];  // 元素的子元素们, 子元素不用addFeature

    // 其他地方需要的状态存储
    _pntPerOfBBox: PntPerOfBBox = { // 元素距离包围盒的上下左右边距的百分比
        toLeft: [], // pointArr距离左边百分比
        toRight: [], // pointArr距离上边百分比
    };

    // 节点状态
    isClosePath: boolean = false;  // 是否闭合
    isPointIn: boolean = false; //鼠标是否悬浮在元素上
    isFocused: boolean = false; //元素是否处于焦点状态, 鼠标按在这个元素身上
    isFixedPos: boolean = false;  // 元素是否固定位置,不随画布拖动而移动, 自己可以拖拽,目前也不会随画布缩放而缩放
    isPixelLen: boolean = false;  // 元素是否固定大小, 目前只作用线段粗细和lineDashArr,画布缩放线段粗细不变, 并且不会有bbox控制形变
    isOverflowHidden: boolean = false;  // 子元素超出元素范围,是否隐藏(裁剪掉)
    isStroke: boolean = true;  // 元素是否绘制边框
    isOnlyCenterAdsorb: boolean = false;  // 元素吸附是否只以包围盒的中心点对其
    isOnlyHorizonalMove: boolean = false;  // 元素是否只能水平方向拖拽(移动)
    isOnlyVerticalMove: boolean = false;  // 元素是否只能垂直方向拖拽(移动)
    isHorizonalRevert: boolean = false;  // 元素是否水平翻转了(用于确定元素左上角的点)
    isVerticalRevert: boolean = false;  // 元素是否垂直翻转了(用于确定元素左上角的点)
    isDelWithChild: boolean = true; // 是否删除时候也删除子元素
    _isFlowLineDash: boolean = false; // 虚线位移

    // 节点功能限制
    adsorbTypes: AdsorbType[] = [AdsorbType.GRID];  // 移动时吸附规则  "grid", "feature"
    ctrlTypes: CtrlType[] = [CtrlType.WIDTH_CTRL, CtrlType.HEIGHT_CTRL, CtrlType.SIZE_CTRL, CtrlType.ANGLE_CTRL, CtrlType.PNT_CTRL, CtrlType.BEZIER_CTRL];  // 可用的控制点类型有哪些

    // 节点功能限制
    cbCapture: boolean = true;  // 元素是否可被鼠标捕获(是cbSelect更上级,不能被鼠标捕获自然也不能被选中)
    cbSelect: boolean = true;  // 元素是否可被选中(是否能为focusNode, 就算设置false,也可以有hover效果)
    cbTranslate: boolean = true;  // 元素是否可被位移(被动)
    cbDrag: boolean = true;   // 是否可被鼠标拖拽(主动)
    cbTransform: boolean = true;  // 元素是否可被形变缩放
    cbTransformChild: boolean = true; // 元素的子元素是否可被形变缩放
    cbDrawOutScreen: boolean = false;  // 是否可以在屏幕外渲染(非必要不增加计算量)
    cbDrawMiniFeature: boolean = false;  // 是否元素被缩放到太小时还要渲染(非必要不增加渲染量)

    // 相对坐标[]
    constructor(pointArr: IRelativePos[] = []) {
        pointArr.forEach(p => this.addPoint(p))
        this.id = Utils.getUuid();
    }

    /**
     * 旋转元素
     * @param angle 要旋转的角度
     * @param O 旋转所围绕的中心点
     */
    rotate(angle: number = this.angle, O: IPoint = Feature.getCenterPos(this.pointArr)) {
        this.pointArr = this.getPointArr(this.pointArr, angle, O)
        this.angle += angle;
        this.children.forEach(cf => {
            if (!cf.isFocused) cf.rotate(angle, O) // 子元素递归旋转
        })
        this.dispatch(new CustomEvent(Events.ROTATE, { detail: '' }))
        return this;
    }

    /**
     * 元素位移
     * @param offsetX 
     * @param offsetY 
     * @returns 
     */
    translate(offsetX: number = 0, offsetY: number = 0) {
        if (!this.cbTranslate) return;
        if (this.group && this.group.isPixelLen) {  // 如果元素在组内并且限制了大小,则只能在组的范围内拖动
            const gExtent = Feature.getRectWrapExtent(this.group.pointArr, -this.group.padding);
            const extent = Feature.getRectWrapExtent(this.pointArr);
            if (extent[0] + offsetX < gExtent[0]) {
                offsetX = gExtent[0] - extent[0]
            }
            if (extent[1] + offsetX > gExtent[1]) {
                offsetX = gExtent[1] - extent[1]
            }
            if (extent[2] + offsetY < gExtent[2]) {
                offsetY = gExtent[2] - extent[2]
            }
            if (extent[3] + offsetY > gExtent[3]) {
                offsetY = gExtent[3] - extent[3]
            }
        }
        this.pointArr = this.pointArr.map(p => ({  // pointArr的点
            ...p,
            x: !this.isOnlyVerticalMove ? p.x += offsetX : p.x,
            y: !this.isOnlyHorizonalMove ? p.y += offsetY : p.y,
        }))
        this.bezierPnts = this.bezierPnts.map(p => ({  // 贝塞尔控制点
            ...p,
            x: !this.isOnlyVerticalMove ? p.x += offsetX : p.x,
            y: !this.isOnlyHorizonalMove ? p.y += offsetY : p.y,
        }))
        if (this.position) { // rect,pnt元素需要更新坐标
            this.position.x += offsetX;
            this.position.y += offsetY;
        }
        this.children.forEach(cf => cf.translate(offsetX, offsetY)) // 子元素递归偏移
        this.dispatch(new CustomEvent(Events.TRANSLATE, { detail: { offsetX, offsetY } }))
        return this;
    }

    /**
     * 元素渲染到画布上
     * @param ctx canvas 上下文
     * @param pointArr 元素的点坐标
     * @param lineWidth 元素的边框,线段粗细
     * @param r 元素的圆角 一般Rect用
     * @returns 
     */
    draw(ctx: CanvasRenderingContext2D, pointArr: IPixelPos[], lineWidth: number, lineDashArr: number[]): void | Path2D {
        const path = new Path2D();
        pointArr.forEach((p, i) => {
            if (i == 0) {
                path.moveTo(p.x, p.y)
            } else {
                path.lineTo(p.x, p.y)
            }
        })
        this.setStyle(ctx, lineWidth, lineDashArr);  //设置一些样式, 描边填充等
        if (this.isClosePath) {
            path.closePath();  // 线段闭合
            ctx.fill(path);  // 填充
        }
        if (this.isStroke) {
            if (this.lineWidth > 0) {
                ctx.stroke(path);  // 描边
            }
            if (this.fakeLineWidth) { // 重画一个透明的粗线段,更方便选取
                ctx.lineWidth = this.fakeLineWidth;
                ctx.strokeStyle = "transparent"
                ctx.stroke(path);
            }
        }
        this.setPointIn(ctx, pointArr, path)
        this.setFlowLineDash();
        return path;
    }

    // 元素样式
    private setStyle(ctx: CanvasRenderingContext2D, lineWidth = 0, lineDashArr: number[] = [0, 0]) {  // 每多一个属性就会增加一点性能消耗
        ctx.lineWidth = lineWidth;
        ctx.setLineDash(lineDashArr)
        ctx.lineCap = this.lineCap;
        ctx.lineJoin = this.lineJoin;
        ctx.globalAlpha = this.opacity;
        ctx.lineDashOffset = this.lineDashOffset;
        ctx.shadowColor = this.shadowColor;
        ctx.shadowBlur = this.shadowBlur;
        ctx.shadowOffsetX = this.shadowOffsetX;
        ctx.shadowOffsetY = this.shadowOffsetY;
        // ctx.filter = this.filter;

        if (this.isClosePath) {
            ctx.strokeStyle = this._getFillStyle(this.strokeStyle, ctx);
            if (this.isPointIn) {
                ctx.fillStyle = this._getFillStyle(this.hoverStyle, ctx);
                if (Feature.Gls.focusNode === this) {
                    ctx.fillStyle = this._getFillStyle(this.focusStyle, ctx);
                }
            } else {
                ctx.fillStyle = this._getFillStyle(this.fillStyle, ctx);
            }
        } else {
            if (this.isPointIn) {
                ctx.strokeStyle = this._getFillStyle(this.hoverStyle, ctx);
                if (Feature.Gls.focusNode === this) {
                    ctx.strokeStyle = this._getFillStyle(this.focusStyle, ctx);
                }
            } else {
                ctx.strokeStyle = this._getFillStyle(this.strokeStyle, ctx);
            }
        }
    }

    /**          
     * 判断鼠标是否悬浮到元素上
     * @param ctx 
     * @param path 
     */
    private setPointIn(ctx: CanvasRenderingContext2D, pointArr: IPixelPos[], path?: Path2D) {
        if (Feature.Render && Feature.Render?.className === ClassName.GRIDSYSTEM && this.cbCapture) {
            const mousePos = Feature.Render.mousePos;
            if (!Utils.isPntInPolygon(mousePos, Feature.getRectWrapPoints(pointArr))) {
                this.isPointIn = false;
                return;
            };
            let isPointIn;
            if (path) {
                if (this.isClosePath) {
                    isPointIn = ctx.isPointInPath(path, mousePos.x, mousePos.y)
                } else {
                    isPointIn = ctx.isPointInStroke(path, mousePos.x, mousePos.y)
                }
            } else {
                if (this.isClosePath) {
                    isPointIn = ctx.isPointInPath(mousePos.x, mousePos.y)
                } else {
                    isPointIn = ctx.isPointInStroke(mousePos.x, mousePos.y)
                }
            }
            this.isPointIn = isPointIn;
        }
    }

    /**
     * 元素添加点坐标
     * @param point 坐标点
     * @param isLimitDistance 两点之间最小距离
     * @returns 
     */
    addPoint(point: IRelativePos, isLimitDistance = Feature.minDistanceOfTwoPnt > 0) {
        if (isLimitDistance && this.pointArr.length > 2) {
            const prevPnt = this.pointArr[this.pointArr.length - 1];
            if (prevPnt && Utils.getLenOfTwoPnts(point, prevPnt) < Feature.minDistanceOfTwoPnt) {
                console.warn("两点距离太近了, 就不添加了!");
                return;
            }
        }
        this.pointArr.push(point);
        return this;
    }

    setFlowLineDash(enable?: boolean, speed = .1, direction = true) {
        if (!this.lineDashArr || this.lineDashArr.length != 2) return;
        if (enable || this._isFlowLineDash) {
            this._isFlowLineDash = true;
            speed = Feature.Render.getRatioSize(speed);
            if (direction) {
                this.lineDashOffset -= speed;
            } else {
                this.lineDashOffset += speed;
            }
        }
        if (enable === false) {
            this._isFlowLineDash = false;
        }
        return this;
    }

    /**
     * 元素添加子元素
     * @param feature 子元素
     * @param translate 是否移动到父元素的左上角,即以父元素的左上角为原点
     * @returns 
     */
    addChild(feature: Feature, translate = true) {
        if (this === feature) throw "不能添加自己!";  // 已经存在不添加
        if (this.children.find(cf => cf === feature)) throw "已经存在了!";  // 已经存在不添加
        this.children.push(feature);
        if (translate) {
            const [leftTop] = Feature.getRectWrapPoints(this.pointArr);
            feature.translate(leftTop.x, leftTop.y);   // 移动到父元素左上角
        }
        return this;
    }
    removeChild(feature?: Feature) { // 删除指定子元素
        if (feature) {
            this.children.splice(this.children.findIndex(cf => cf == feature), 1);
        } else {
            this.children = []
        }
        return this;
    }
    getBasicChilds() {   // 获取基本元素,不包括点,或者其他类型元素
        return this.children.filter(f => Utils.isBasicFeature(f));
    }

    /**
     * 元素导出Svg格式
     * @param pointArr 
     * @param lineWidth 
     * @returns 
     */
    getSvg(pointArr: IPixelPos[] = [], lineWidth: number = 1) {
        let path = ''
        pointArr.forEach((p, i) => {
            if (i === 0) {
                path += `M ${p.x} ${p.y} `
            } else {
                path += `L ${p.x} ${p.y} `
            }
        })
        if (this.isClosePath) {
            path += ' Z'
        }
        return `
        <path d="${path}" 
            stroke="${this.strokeStyle}" 
            stroke-width="${lineWidth}" 
            fill="${this.isClosePath && !(this.fillStyle instanceof CanvasPattern) ? this.fillStyle : 'transparent'}" 
            stroke-linecap="${this.lineCap}" 
            stroke-linejoin="${this.lineJoin}" 
            stroke-dasharray="${this.lineDashArr}" 
            stroke-dashoffset="${this.lineDashOffset}"
            opacity="${this.opacity}"
        />`
    }
    /**
     * 元素的镜像翻转
     * @param direction 翻转的方向
     * @param center 翻转围绕的中心点
     * @param isParent 是否是主元素,因为子元素也要跟着翻转
     */
    revert(direction: AlignType, center?: IPoint, isParent = true) {
        if (!center) center = Feature.getCenterPos(this.pointArr);  // 获取包围盒中心点
        this.children.forEach(cf => {  // 遍历子元素翻转,如果他有子元素的话
            cf.revert(direction, center, false)
        })
        switch (direction) {
            case AlignType.HORIZONAL: {  // 水平翻转
                const centerPos = center as IPoint;
                this.pointArr = this.pointArr.map(p => ({ x: 2 * centerPos.x - p.x, y: p.y }))
                this.isHorizonalRevert = !this.isHorizonalRevert;
                this.angle = 360 - this.angle;
                break;
            }
            case AlignType.VERTICAL: { // 垂直翻转
                const centerPos = center as IPoint;
                this.pointArr = this.pointArr.map(p => ({ x: p.x, y: 2 * centerPos.y - p.y }))
                this.isVerticalRevert = !this.isVerticalRevert;
                this.angle = 360 - this.angle;
                break;
            }
            default:
                break;
        }
        if (isParent && Utils.isBasicFeature(this) || this.className == ClassName.SELECTAREA) {
            Feature.Gls.enableBbox();
            Feature.Gls.enableBbox(this as unknown as Feature);
        }
    }

    protected rotateCtx(ctx: CanvasRenderingContext2D, O: IPoint, angle = this.angle) { // 以O点去旋转内容，文字或者图片
        if (angle) {
            ctx.translate(O.x, O.y)
            ctx.rotate(angle * Math.PI / 180)
            ctx.translate(-O.x, -O.y)
        }
    }

    /**
     * 
     * @param pointArr 元素的点集合
     * @param angle 旋转多少度
     * @param O 围绕那个点旋转,默认元素中心点
     * @returns poitnArr
     */
    getPointArr(pointArr = this.pointArr, angle = 0, O: IPoint = Feature.getCenterPos(this.pointArr)) { // 一个点围绕某个点旋转angle角度, 返回旋转后的pointArr(相对坐标)
        if (angle === 0) return pointArr;
        return pointArr.map(p => ({
            ...p,
            ...Utils.getRotatePnt(O, p, angle)
        }))
    }

    // 订阅事件
    on(type: string, callback: Function) {
        if (!this._listeners[type]) {
            this._listeners[type] = [];
        }
        this._listeners[type].push(callback);
    }
    // 取消订阅事件
    off(type: string, callback: Function) {
        if (!this._listeners[type]) return;
        const index = this._listeners[type].indexOf(callback);
        if (index != -1) {
            this._listeners[type].splice(index, 1);
        }
    }
    // 触发事件
    dispatch(event: CustomEvent) {
        if (this._listeners[event.type]) {
            this._listeners[event.type].forEach(callback => {
                callback(event);
            });
        }
    }

    toStraightPoints(d = 2) {  // 判断离散点是否趋近于直线,如果是,简化为一条直线
        const { m, b } = Utils.getLinearRegression(this.pointArr);
        let totalDistance = 0;
        for (let i = 0; i < this.pointArr.length; i++) {
            const my = m * this.pointArr[i].x + b  // 斜率上的y
            let d = Math.abs(this.pointArr[i].y - my) / Math.sqrt(m * m + 1);  // 点到直线的距离
            totalDistance += d;
        }
        if ((totalDistance/this.pointArr.length) < d) {
            const startPnt = this.pointArr[0]
            const endPnt = this.pointArr[this.pointArr.length - 1]
            this.pointArr = [startPnt, endPnt]
            console.log("近似直线");
            return true;
        }
    }

    toCirclePoints(d = .3) {
        const center = Feature.getCenterPos(this.pointArr);
        const dists = this.pointArr.map(p => Utils.getLenOfTwoPnts(p, center));
        const variance = Utils.getVariance(dists);
        const average = Utils.getAverage(dists)
        const start2endD = Utils.getLenOfTwoPnts(this.pointArr[0], this.pointArr[this.pointArr.length - 1])  // 判断不是圆弧
        if (variance / average < d && start2endD / average < 1) {
            console.log("近似圆");
            this.lineWidthArr = []
            this.pointArr = Utils.getCirclePoints(center.x, center.y, average * 2, average * 2)
            return true;
        }
    }

    toRectPoints(d = 6, per = .7, squareD = .02) {  // d: 点坐标距离包围盒边界的偏离值   per  有百分之多少的点在d的范围内, 
        const [left, right, top, bottom] = Feature.getRectWrapExtent(this.pointArr);
        const center = Feature.getCenterPos(this.pointArr) as IPoint & ISize;
        const leftPnts = this.pointArr.filter(p => {
            if (Math.abs(p.x - left) < d) {
                return p
            }
        })
        const rightPnts = this.pointArr.filter(p => {
            if (Math.abs(p.x - right) < d) {
                return p
            }
        })
        const topPnts = this.pointArr.filter(p => {
            if (Math.abs(p.x - top) < d) {
                return p
            }
        })
        const bottomPnts = this.pointArr.filter(p => {
            if (Math.abs(p.x - bottom) < d) {
                return p
            }
        })

        if(leftPnts){

        }

        // if (pointArr.length / this.pointArr.length > per) {
        //     if (center.width / (center.width + center.height) / 2 - center.height / (center.width + center.height) / 2 < squareD) {
        //         console.log("近似正方形");
        //         center.width = center.height
        //     }
        //     this.pointArr = Utils.getRectPoints(center, { width: center.width, height: center.height });
        //     console.log("近似矩形");
        //     return true;
        // }
    }

    toLaser() {  // 消失的尾迹
        let timer = 0, timerOfFriction = 0;
        timer = setTimeout(() => {
            timerOfFriction = setInterval(() => {
                this.pointArr.shift();
                if (this.pointArr.length <= 0) {
                    clearInterval(timerOfFriction)
                    clearTimeout(timer)
                    Feature.Gls.removeFeature(this);
                }
            }, 20)
        }, 350)
    }

    clearListener() {
        Object.keys(this._listeners).forEach(type => {
            delete this._listeners[type]
        })
    }

    _getFillStyle(fill: string | Function, ctx: CanvasRenderingContext2D): string | CanvasPattern {
        return typeof fill == 'string' ? fill : fill(ctx)
    }

    destroy() {
        this.clearListener();
    };
}


export default Feature